

<!DOCTYPE html>
<html lang="zh-CN">

<head><meta name="generator" content="Hexo 3.9.0">
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>从源码的角度看 Fragment 返回栈 - 杨云召 | 博客</title>
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="google" content="notranslate">

  
  
  <meta name="description" content="从源码的角度看 Fragment 返回栈。


前言
..."> 
  
  <meta name="author" content="杨云召（Flywith24）"> 

  
    <link rel="icon" href="/images/icons/favicon-16x16.png" type="image/png" sizes="16x16">
  
  
    <link rel="icon" href="/images/icons/favicon-32x32.png" type="image/png" sizes="32x32">
  
  
    <link rel="apple-touch-icon" href="/images/icons/apple-touch-icon.png" sizes="180x180">
  
  
    <meta rel="mask-icon" href="/images/icons/stun-logo.svg" color="#333333">
  
  
    <meta rel="msapplication-TileImage" content="/images/icons/favicon-144x144.png">
    <meta rel="msapplication-TileColor" content="#000000">
  

  <link rel="stylesheet" href="/css/style.css">

  
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1445822_hhodbqn7tit.css">
  

  
  
  <link rel="stylesheet" href="https://cdn.bootcss.com/fancybox/3.5.7/jquery.fancybox.min.css">
  

  
  
  <link rel="stylesheet" href="https://cdn.bootcss.com/highlight.js/9.18.1/styles/xcode.min.css">
  

  <script>
    var CONFIG = window.CONFIG || {};
    var ZHAOO = window.ZHAOO || {};
    CONFIG = {
      isHome: false,
      fancybox: true,
      pjax: false,
      lazyload: {
        enable: true,
        loadingImage: '/images/theme/loading.gif',
      },
      donate: {
        enable: true,
        alipay: 'https://gitee.com/flywith24/Album/raw/master/img/20201015113301.jpg',
        wechat: 'https://gitee.com/flywith24/Album/raw/master/img/20201015112814.png'
      },
      motto: {
        api: '',
        default: '不人云亦云，只求接近真相.'
      },
      galleries: {
        enable: true
      },
      fab: {
        enable: true,
        alwaysShow: false
      },
      carrier: {
        enable: true
      },
      daovoice: {
        enable: true
      }
    }
  </script>

  

  
</head></html>
<body class="lock-screen">
  <div class="loading"></div>
  


<nav class="navbar">
  <div class="left"></div>
  <div class="center">从源码的角度看 Fragment 返回栈</div>
  <div class="right">
    <i class="iconfont iconmenu j-navbar-menu"></i>
  </div>
</nav>

  <nav class="menu">
  <div class="menu-wrap">
    <div class="menu-close">
      <i class="iconfont iconbaseline-close-px"></i>
    </div>
    <ul class="menu-content">
      
      
      
      
      <li class="menu-item"><a href="/ " class="underline"> 首页</a></li>
      
      
      
      
      <li class="menu-item"><a href="/archives " class="underline"> 归档</a></li>
      
      
      
      
      <li class="menu-item"><a href="/tags " class="underline"> 标签</a></li>
      
      
      
      
      <li class="menu-item"><a href="/categories " class="underline"> 分类</a></li>
      
      
      
      
      <li class="menu-item"><a href="/about " class="underline"> 关于</a></li>
      
    </ul>
    <div class="menu-copyright"><p>Copyright© 2017 - 2020 ❤️ Flywith24</p></div>
  </div>
</nav>
  <main id="main">
  <div class="container" id="container">
    <article class="article">
  <div class="wrap">
    <section class="head">
  <img   class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20201015095213.png" src=""  draggable="false">
  <div class="head-mask">
    <h1 class="head-title">从源码的角度看 Fragment 返回栈</h1>
    <div class="head-info">
      <span class="post-info-item"><i class="iconfont iconcalendar"></i>三月 16, 2020</span
        class="post-info-item">
      
      <span class="post-info-item"><i class="iconfont iconfont-size"></i>7686</span>
    </div>
  </div>
</section>
    <section class="main">
      <section class="content">
        <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p><a href="https://juejin.im/post/5e6bae35f265da572a0d11ad" target="_blank" rel="noopener">上一篇</a> 我们介绍了 <code>OnBackPressedDispather</code> ，那么今天我们来正式地从源码的角度看看 fragment 的返回栈吧。由于其主流程和生命周期差不多，因此本文将详细地分析返回栈相关的源码，并插入大量源码。建议将生命周期流程熟悉后阅读本文。文末提供单返回栈和多返回栈的 <a href="https://github.com/Flywith24/Flywith24-Fragment-Demo" target="_blank" rel="noopener">demo</a></p>
</blockquote>
<p>如果您对 activity 对任务栈和返回栈不是很了解，可以移步  <a href="https://medium.com/androiddevelopers/tasks-and-the-back-stack-dbb7c3b0f6d4" target="_blank" rel="noopener">Tasks and the Back Stack</a></p>
<h2 id="小问号你是否有很多朋友？"><a href="#小问号你是否有很多朋友？" class="headerlink" title="小问号你是否有很多朋友？"></a>小问号你是否有很多朋友？</h2><p>在分析源码之前，我们先来思考几个问题。</p>
<ul>
<li>返回栈中的元素是什么？</li>
<li>谁来管理 fragment 的返回栈？</li>
<li>如何返回？</li>
</ul>
<h3 id="返回栈中的元素是什么？"><a href="#返回栈中的元素是什么？" class="headerlink" title="返回栈中的元素是什么？"></a>返回栈中的元素是什么？</h3><p>返回栈，顾名思义，是一个栈结构。所以我们要搞清楚，这个栈结构到底存的是什么。</p>
<p>我们都知道，使用 fragment 的返回栈需要调用 <code>addToBackStack(&quot;&quot;)</code> 方法</p>
<p>在 <a href="https://juejin.im/post/5e67523551882549003d2c4f" target="_blank" rel="noopener">从源码角度看 Fragment 生命周期</a> 一文中，我们提到了 FragmentTransaction ，它是一个「事务」的模型，事务可以回滚到之前的状态。所以当触发返回操作时，就是将之前提交的事务进行回滚。</p>
<p><code>FragmentTransaction</code> 的实现类为 <code>BackStackRecord</code> ，所以 <strong>fragment 的返回栈其实存放的就是 BackStackRecord</strong> </p>
<p>作为返回栈的元素，BackStackRecord 实现了FragmentManager.BackStackEntry 接口</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf0e912bd4?w=748&h=171&f=png&s=27904" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="BackStackRecord"></p>
<p>从 <code>BackStackRecord</code> 的定义我们可以发现 <code>BackStackRecord</code> 有三种身份</p>
<ul>
<li>继承了 <code>FragmentTransaction</code>，即是事务，保存了整个事务的全部操作</li>
<li>实现了 <code>FragmentManager.BackStackEntry</code> ，作为回退栈的元素</li>
<li>实现了<code>OpGenerator</code> ，可以生成 <code>BackStackRecord</code> 列表，后文详细介绍</li>
</ul>
<h3 id="谁来管理-fragment-的返回栈？"><a href="#谁来管理-fragment-的返回栈？" class="headerlink" title="谁来管理 fragment 的返回栈？"></a>谁来管理 fragment 的返回栈？</h3><p>我们已经知道 fragment 的返回栈其实存放的是 BackSrackRecord , 那么谁来管理 fragment 的返回栈？</p>
<p><code>FragmentManager</code> 用于管理 fragment ，所以 <strong>fragment 返回栈也应该由 FragmentManager 管理</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//FragmentManager.java</span><br>ArrayList&lt;BackStackRecord&gt; mBackStack;<br></code></pre></td></tr></table></figure>

<p>其实触发 fragment 的返回逻辑有两种途径</p>
<ul>
<li><p>开发主动调用 fragment 的返回方法</p>
</li>
<li><p>用户按返回键触发</p>
</li>
</ul>
<p>后文我们会从这两个角度分析一下 fragment 中的返回栈逻辑究竟是怎样的</p>
<h3 id="如何返回？"><a href="#如何返回？" class="headerlink" title="如何返回？"></a>如何返回？</h3><p>我们已经知道返回栈中的元素是 <code>BackStackRecord</code> ，也清楚了是 <code>FragmentManager</code> 来管理返回栈。那么如果让我们来实现「返回」逻辑，应该如何做？</p>
<p>首先我们要清楚所谓的「返回」是对事务的回滚，即 <strong>对 commit 事务的内部逻辑执行相应的「逆操作」</strong>。</p>
<p>例如</p>
<p>addFragment←→removeFragment</p>
<p>showFragment←→hideFragment</p>
<p>attachFragment←→detachFragment</p>
<p>有的小伙伴可能会疑惑 replace 呢？</p>
<p><code>expandReplaceOps</code> 方法会把 replace 替换(目标 fragment 已经被 add )成相应的 remove 和 add 两个操作，或者(目标 fragment 没有被 add )只替换成 add 操作</p>
<h2 id="popBackStack-系列方法"><a href="#popBackStack-系列方法" class="headerlink" title="popBackStack 系列方法"></a>popBackStack 系列方法</h2><p><code>FragmentManager</code> 中提供了<code>popBackStack</code> 系列方法</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf0f7fecec?w=523&h=207&f=png&s=41693" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="popBackStack系列方法"></p>
<p>是否觉得很眼熟？提交事务也有类似的api，commit 系列方法</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf0f5fe236?w=425&h=116&f=png&s=19768" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="commit系列方法"></p>
<p>这里分别提供了同步和异步的方法，可能有读者会疑惑，同样是对事务的操作，一个为提交，一个为回滚，为什么一个封装到了 <code>FragmentManager</code> 中，一个却在 <code>FragmentTransaction</code> 中。既然都是对事务的操作，应该都放在FragmentManager 中。我认为可能为了api使用的方便，使得 <code>FragmentManager</code> 开启事务的链式调用一气呵成。各位有什么想法欢迎在评论区留言。</p>
<p>这里主要介绍一下 popBackStack(String name, int flag)</p>
<p>name 为 addToBackStack(String name) 的参数，通过 name 能找到回退栈的特定元素，flag可以为 0 或者<code>FragmentManager.POP_BACK_STACK_INCLUSIVE</code>，0 表示只弹出该元素以上的所有元素，<code>POP_BACK_STACK_INCLUSIVE</code> 表示弹出包含该元素及以上的所有元素。这里说的弹出所有元素包含回退这些事务。如果这么说比较抽象的话，看图</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">//flag 传入0，弹出 ♥2 上的所有元素</span><br>childFragmentManager.popBackStack(<span class="hljs-string">"♥"</span>, <span class="hljs-number">0</span>)<br></code></pre></td></tr></table></figure>

<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf111e4eca?w=544&h=968&f=gif&s=332027" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="flag为0"></p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">//flag 为 POP_BACK_STACK_INCLUSIVE 弹出包括该元素及及以上的元素</span><br>childFragmentManager.popBackStack(<span class="hljs-string">"♥"</span>,  androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE)<br></code></pre></td></tr></table></figure>

<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf11573c7e?w=544&h=968&f=gif&s=307357" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="flag为1"></p>
<h2 id="走进源码"><a href="#走进源码" class="headerlink" title="走进源码"></a>走进源码</h2><h3 id="1-popBackStack-逻辑"><a href="#1-popBackStack-逻辑" class="headerlink" title="1. popBackStack() 逻辑"></a>1. popBackStack() 逻辑</h3><p>在分析返回栈源码之前我们回顾一下 FragmentManager 提交事务到 fragment 各个生命周期的流程</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf128028ab?w=1137&h=1745&f=png&s=148227" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="异步"></p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf3c14d698?w=1078&h=1232&f=png&s=120922" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="commitNow"></p>
<p>下面我们看看 popBackStack 的源码</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf3e264529?w=1111&h=744&f=png&s=152765" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="popBackStack源码"></p>
<p>等等，这个 enqueueAction 有些眼熟…</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf4261e2a2?w=582&h=144&f=png&s=16887" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="commit"></p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf43057f36?w=792&h=404&f=png&s=74980" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="commitInternal"></p>
<p>看来提交事务和回滚事务的流程基本是相同的，只是传递的 action 不同</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf463c7504?w=818&h=722&f=png&s=108117" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="enqueueAction"></p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/16/170df028ee01dd57?w=1081&h=713&f=png&s=131939" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="OpGenerator"></p>
<p>由源码可知，<code>OpGenerator</code> 是一个接口，其内只有一个 <code>generateOps</code> 方法，用于生成事务列表以及对应的该事务是否是弹出的。有两个实现类</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf5579b571?w=1319&h=139&f=png&s=46323" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="OpGenerator实现类"></p>
<p>由此可见 commit 调用的为 <code>BackStackRecord</code> 的 <code>generateOps</code> 方法，<code>popBackStack</code> 调用的是 <code>PopBackStackState</code> 中的  <code>generateOps</code> </p>
<p>前者的逻辑很简单，向 records list 中添加数据， isRecordPop list 全部传入 false</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">records.add(<span class="hljs-keyword">this</span>);<br>isRecordPop.add(<span class="hljs-keyword">false</span>);<br></code></pre></td></tr></table></figure>

<p>后者的逻辑稍微复杂些，其内部调用了 <code>popBackStackState</code> 方法</p>
<p>如果是 <code>popBackStack</code> 方法 ，则将 <code>FragmentManager</code> 的返回栈列表（<code>mBackStack</code>）的栈顶移除， <code>isRecordPop</code> list 全部传入 true</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> last = mBackStack.size() - <span class="hljs-number">1</span>;<br>records.add(mBackStack.remove(last));<br>isRecordPop.add(<span class="hljs-keyword">true</span>);<br></code></pre></td></tr></table></figure>

<p>如果传入的 name 或 id 有值，且 flag 为 0，则找到返回栈（倒序遍历）中第一个符合 name 或 id 的位置，并将该位置上方的所有 <code>BackStackRecord</code> 并添加到 <code>record</code> list 中，同时 <code>isRecordPop</code> list 全部传入 true</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java">index = mBackStack.size() - <span class="hljs-number">1</span>;<br><span class="hljs-keyword">while</span> (index &gt;= <span class="hljs-number">0</span>) &#123;<br>    BackStackRecord bss = mBackStack.get(index);<br>    <span class="hljs-keyword">if</span> (name != <span class="hljs-keyword">null</span> &amp;&amp; name.equals(bss.getName())) &#123;<br>        <span class="hljs-keyword">break</span>;<br>    &#125;<br>    <span class="hljs-keyword">if</span> (id &gt;= <span class="hljs-number">0</span> &amp;&amp; id == bss.mIndex) &#123;<br>        <span class="hljs-keyword">break</span>;<br>    &#125;<br>    index--;<br>&#125;<br><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = mBackStack.size() - <span class="hljs-number">1</span>; i &gt; index; i--) &#123;<br>  records.add(mBackStack.remove(i));<br>  isRecordPop.add(<span class="hljs-keyword">true</span>);<br>&#125;<br></code></pre></td></tr></table></figure>

<p>如果传入的 name 或 id 有值，且 flag 为 <code>POP_BACK_STACK_INCLUSIVE</code>，则在上一条获取位置的基础上继续遍历，直至栈底或者遇到不匹配的跳出循环，接着出栈所有 <code>BackStackRecord</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//index 操作与上方相同，先找到返回栈（倒序遍历）中第一个符合 name 或 id 的位置</span><br><span class="hljs-keyword">if</span> ((flags &amp; POP_BACK_STACK_INCLUSIVE) != <span class="hljs-number">0</span>) &#123;<br>    index--;<br>    <span class="hljs-comment">// 继续遍历 mBackStack 直至栈底或者遇到不匹配的跳出循环</span><br>    <span class="hljs-keyword">while</span> (index &gt;= <span class="hljs-number">0</span>) &#123;<br>        BackStackRecord bss = mBackStack.get(index);<br>        <span class="hljs-keyword">if</span> ((name != <span class="hljs-keyword">null</span> &amp;&amp; name.equals(bss.getName()))<br>                || (id &gt;= <span class="hljs-number">0</span> &amp;&amp; id == bss.mIndex)) &#123;<br>            index--;<br>            <span class="hljs-keyword">continue</span>;<br>        &#125;<br>        <span class="hljs-keyword">break</span>;<br>    &#125;<br>&#125;<br><span class="hljs-comment">//后续出栈逻辑与上方相同</span><br></code></pre></td></tr></table></figure>

<p>可以配合上面的动图理解</p>
<p>入栈和出栈后续的逻辑大体是相同的，只是根据 isPop 的正负出现了分支，出栈调用的是 executePopOps</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/16/170df06c04191fff?w=972&h=682&f=png&s=132928" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>上文我们有提到，「返回」逻辑实际上就是执行提交事务内部操作逻辑的「逆操作」</p>
<p>那么接下的逻辑就很清晰了，根据不同的 mCmd 执行相应的逆操作</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">executePopOps</span><span class="hljs-params">(<span class="hljs-keyword">boolean</span> moveToState)</span> </span>&#123;<br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> opNum = mOps.size() - <span class="hljs-number">1</span>; opNum &gt;= <span class="hljs-number">0</span>; opNum--) &#123;<br>        <span class="hljs-keyword">final</span> Op op = mOps.get(opNum);<br>        Fragment f = op.mFragment;<br>        <span class="hljs-keyword">switch</span> (op.mCmd) &#123;<br>            <span class="hljs-keyword">case</span> OP_ADD:<br>                mManager.removeFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_REMOVE:<br>                mManager.addFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_HIDE:<br>                mManager.showFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_SHOW:<br>                mManager.hideFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_DETACH:<br>                mManager.attachFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_ATTACH:<br>                mManager.detachFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_SET_PRIMARY_NAV:<br>                mManager.setPrimaryNavigationFragment(<span class="hljs-keyword">null</span>);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_UNSET_PRIMARY_NAV:<br>                mManager.setPrimaryNavigationFragment(f);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> OP_SET_MAX_LIFECYCLE:<br>                mManager.setMaxLifecycle(f, op.mOldMaxState);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">default</span>:<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Unknown cmd: "</span> + op.mCmd);<br>        &#125;<br>        <span class="hljs-keyword">if</span> (!mReorderingAllowed &amp;&amp; op.mCmd != OP_REMOVE &amp;&amp; f != <span class="hljs-keyword">null</span>) &#123;<br>            mManager.moveFragmentToExpectedState(f);<br>        &#125;<br>    &#125;<br>    <span class="hljs-keyword">if</span> (!mReorderingAllowed &amp;&amp; moveToState) &#123;<br>        mManager.moveToState(mManager.mCurState, <span class="hljs-keyword">true</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>后面的逻辑就完全一样了</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf65e331c7?w=1137&h=1585&f=png&s=127564" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="popBackStack"></p>
<h3 id="2-fragment-是怎样拦截-activity-的返回逻辑的？"><a href="#2-fragment-是怎样拦截-activity-的返回逻辑的？" class="headerlink" title="2. fragment 是怎样拦截 activity 的返回逻辑的？"></a>2. fragment 是怎样拦截 activity 的返回逻辑的？</h3><p>在 <a href="https://juejin.im/post/5e6bae35f265da572a0d11ad" target="_blank" rel="noopener">【背上Jetpack之OnBackPressedDispatcher】Fragment 返回栈预备篇</a> 一文中我们介绍了 <code>OnBackPressedDispatcher</code> </p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf6fd444f6?w=1138&h=544&f=png&s=96482" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="ComponetActivity"></p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf70da7341?w=642&h=512&f=png&s=71452" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>activity 的 <code>onBackPressed</code> 的逻辑主要分为两部分，判断所有注册的 <code>OnBackPressedCallback</code> 是否有 enabled 的，如果有则拦截，不执行后续逻辑；</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/16/170df0d07750776b?w=801&h=765&f=png&s=137606" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="fragment 拦截返回逻辑"></p>
<p>否则着执行 mFallbackOnBackPressed.run() ，其内部逻辑为调用 ComponentActivity 父类的 <code>onBackPressed</code> 方法</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf7443eb77?w=715&h=275&f=png&s=39938" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p><strong>所以我们只需看 mOnBackPressedCallbacks（ArrayDeque&lt;OnBackPressedCallback） 是怎样被添加的以及 isEnabled 何时赋值为 true</strong></p>
<p>经过查找我们发现它是在 FragmentManager 的 attachController 调用 <code>addCallback</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">mOnBackPressedDispatcher.addCallback(owner,mOnBackPressedCallback)<br></code></pre></td></tr></table></figure>

<p>进而执行了</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/16/170df099d16ae502?w=1000&h=425&f=png&s=75605" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt><br><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf7c970039?w=822&h=233&f=png&s=41860" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>而 <code>mOnBackPressedCallback</code> 在初始化时 enabled 赋值为 false </p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf8362798b?w=954&h=194&f=png&s=42041" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="mOnBackPressedCallback"></p>
<p><code>isEnadbled</code> 会在返回栈数量大于 0 且其 mParent 为 <code>PrimaryNavigation</code> 时赋值为true</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf9805eb1e?w=796&h=555&f=png&s=94237" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>而返回栈（<code>mBackStack</code>）的赋值在 <code>BackStackRecord</code> 的 <code>generateOps</code> 方法中，且是否添加到返回栈由 <code>mAddToBackStack</code> 这个布尔类型的属性控制</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/16/170df0f5d77e3c7f?w=891&h=544&f=png&s=94633" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf9a0dfa56?w=548&h=215&f=png&s=28095" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p><strong>mAddToBackStack 的赋值在 addToBackStack 方法中，这也解释了为何调用 addToBackStack 方法就能将事务加入返回栈</strong></p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeaf9eb75bde?w=841&h=306&f=png&s=47458" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<blockquote>
<p> 我们来总结一下，fragment 拦截 activity 返回栈是通过 <code>OnBackPressedDispatcher</code> 实现的，如果开启事务调用了 <code>addToBackStack</code> 方法，则 <code>mOnBackPressedCallback</code> 的 <code>isEnabled</code> 属性会赋值为 true，进而起到拦截 activity 返回逻辑的作用。拦截后执行 <code>popBackStackImmediate</code> 方法</p>
<p>而 popBackStack系列方法会调用 popBackStackState 构造 <code>records</code> 和 <code>isRecordPop</code> 列表，<code>isRecordPop</code> 的内部元素的值均为true 后续流程和提交事务是一样的，根据 <code>isRecordPop</code> 值的不同选择执行 <code>executePopOps</code> 或 <code>executeOps</code> 方法</p>
</blockquote>
<h2 id="单返回栈和多返回栈的实现"><a href="#单返回栈和多返回栈的实现" class="headerlink" title="单返回栈和多返回栈的实现"></a>单返回栈和多返回栈的实现</h2><p><a href="https://medium.com/@ianhlake" target="_blank" rel="noopener">Ian Lake</a> 在 <a href="https://www.youtube.com/watch?v=RS1IACnZLy4" target="_blank" rel="noopener">Fragments: Past, Present, and Future (Android Dev Summit ‘19)</a> </p>
<p>有提到未来会提供多返回栈的 api</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/15/170deeafa780da77?w=1844&h=948&f=png&s=634511" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>那么以现有的 api 如何实现多返回栈呢？</p>
<p>首先我要弄清楚怎样才会有多返回栈，根据上文我们知道 <code>FragmentManager</code> 内部持有<code>mBackStack</code> list，这对应着一个返回栈，<strong>如果想要实现多返回栈，则需要多个 FragmentManager</strong>，而多 <code>FragmentManager</code> 则对应多个 fragment</p>
<p>因此我们可以创建多个宿主 frament 作为导航 fragment 这样就可以用不同的宿主 fragment 的 独立的<code>FragmentManager</code> 分别管理各自的返回栈，如果这样说比较抽象，可以参考下图</p>
<p><img class="lazyload" data-original="https://user-gold-cdn.xitu.io/2020/3/16/170defa88bc0e5f9?w=488&h=750&f=gif&s=4633102" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>图中有四个返回栈，其中最外部有一个宿主 fragment ，内部有四个负责导航的 fragment 管理其内部的返回栈，外部的宿主负责协调各个返回栈为空后如何切换至其他返回栈</p>
<p>单返回栈就很容易了，我们只需在同一个 <code>FragmentManager</code> 上添加返回栈即可</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200312175950" src="/2020/03/16/Jetpack-fragment-back-stack/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>详情参照 <a href="https://github.com/Flywith24/Flywith24-Fragment-Demo" target="_blank" rel="noopener">demo</a></p>
<hr>
<h2 id="关于我"><a href="#关于我" class="headerlink" title="关于我"></a>关于我</h2><hr>
<p>我是 <a href="https://flywith24.gitee.io/">Fly_with24</a></p>
<ul>
<li><p><a href="https://juejin.im/user/57c7f6870a2b58006b1cfd6c" target="_blank" rel="noopener">掘金</a></p>
</li>
<li><p><a href="https://www.jianshu.com/u/3d5ad6043d66" target="_blank" rel="noopener">简书</a></p>
</li>
<li><p><a href="https://github.com/Flywith24" target="_blank" rel="noopener">Github</a></p>
</li>
</ul>
      </section>
      <section class="extra">
        
        <ul class="copyright">
  
  <li><strong>本文作者：</strong>杨云召（Flywith24）</li>
  <li><strong>本文链接：</strong><a href="https://flywith24.gitee.io/2020/03/16/Jetpack-fragment-back-stack/index.html">https://flywith24.gitee.io/2020/03/16/Jetpack-fragment-back-stack/index.html</a></li>
  <li><strong>版权声明：</strong>本博客所有文章均采用<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh"
      rel="external nofollow" target="_blank"> BY-NC-SA </a>许可协议，转载请注明出处！</li>
  
</ul>
        
        
        <section class="donate">
  <div class="qrcode">
    <img   class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20201015113301.jpg" src="" >
  </div>
  <div class="icon">
    <a href="javascript:;" id="alipay"><i class="iconfont iconalipay"></i></a>
    <a href="javascript:;" id="wechat"><i class="iconfont iconwechat-fill"></i></a>
  </div>
</section>
        
        
  <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/Jetpack/">Jetpack</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/MVVM/">MVVM</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/androidx/">androidx</a></li></ul>

        
<nav class="nav">
  
    <a href="/2020/03/19/Jetpack-ViewModel-SaveState/"><i class="iconfont iconleft"></i>绝不丢失的状态 androidx SaveState ViewModel-SaveState 分析</a>
  
  
    <a href="/2020/03/14/Jetpack-OnBackPressedDispatcher/">Fragment返回栈预备篇 OnBackPressedDispatcher<i class="iconfont iconright"></i></a>
  
</nav>

      </section>
      
      <section class="comments">
  
  <div class="btn" id="comments-btn">查看评论</div>
  
  
<div id="valine"></div>
<script defer src="//unpkg.com/valine/dist/Valine.min.js"></script>
<script>
  window.onload = function () {
    var loadValine = function () {
      new Valine({
        el: '#valine',
        app_id: "SHA3gzCcxFlqaPcGRnX4UgET-gzGzoHsz",
        app_key: "OYo7ITS4AzTvBUvb1yIY8oaf",
        placeholder: "期待你的留言",
        avatar: "mp",
        pageSize: "10",
        lang: "zh-CN",
      });
    }
    if ( true ) {
      $("#comments-btn").on("click", function () {
        $(this).hide();
        loadValine();
      });
    } else {
      loadValine();
    }
  };
</script>

</section>
      
    </section>
  </div>
</article>
  </div>
</main>
  <footer class="footer">
  <div class="footer-social">
    
    
    
    
    
    <a href="tencent://message/?Menu=yes&uin=1032367864 " target="_blank" onMouseOver="this.style.color= '#12B7F5'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconQQ "></i>
    </a>
    
    
    
    
    
    <a href="javascript:; " target="_blank" onMouseOver="this.style.color= '#8bc34a'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconwechat-fill "></i>
    </a>
    
    
    
    
    
    <a href="https://flywith24.gitee.io/about/ " target="_blank" onMouseOver="this.style.color= '#d32f2f'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconheart "></i>
    </a>
    
    
    
    
    
    <a href="https://github.com/Flywith24 " target="_blank" onMouseOver="this.style.color= '#24292E'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  icongithub-fill "></i>
    </a>
    
    
    
    
    
    <a href="mailto:youngyunzhao@163.com " target="_blank" onMouseOver="this.style.color='#FFBE5B'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconmail"></i>
    </a>
    
  </div>
  <div class="footer-copyright"><p>Copyright© 2017 - 2020 ❤️ Flywith24</p></div>
</footer>
  
      <div class="fab fab-plus">
    <i class="iconfont iconplus"></i>
  </div>
  
  <div class="fab fab-daovoice">
    <i class="iconfont iconcomment"></i>
  </div>
  
  <div class="fab fab-up">
    <i class="iconfont iconcaret-up"></i>
  </div>
  
<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"jsonPath":"/live2dw/assets/shizuku.model.json"},"display":{"position":"left","width":150,"height":300},"mobile":{"show":"tfalse"},"log":false});</script></body>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>




<script src="https://cdn.bootcdn.net/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js"></script>




<script src="https://cdn.bootcss.com/fancybox/3.5.7/jquery.fancybox.min.js"></script>




<script src="/js/utils.js"></script>
<script src="/js/modules.js"></script>
<script src="/js/zui.js"></script>
<script src="/js/script.js"></script>




<script>
  (function (i, s, o, g, r, a, m) {
    i["DaoVoiceObject"] = r;
    i[r] = i[r] || function () {
      (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o), m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    a.charset = "utf-8";
    m.parentNode.insertBefore(a, m)
  })(window, document, "script", ('https:' == document.location.protocol ? 'https:' : 'http:') +
    "//widget.daovoice.io/widget/0f81ff2f.js", "daovoice")
  daovoice('init', {
    app_id: "7785620b"
  }, {
    launcher: {
      disableLauncherIcon: true,
    },
  });
  daovoice('update');
</script>



<script>
  (function () {
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
      bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
    } else {
      bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
  })();
</script>


<script>
  var _hmt = _hmt || [];
  (function () {
    var hm = document.createElement("script");
    hm.src = "https://hm.baidu.com/hm.js?4c204d8bc027a0455b5fc642ac334ca8";
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(hm, s);
  })();
</script>










</html>